
/*
	(c) Galustyan Sergey 2009
*/

#pragma once

#include <atlbase.h>
#include <boost/shared_ptr.hpp>
#include <boost/foreach.hpp>
#include <boost/tuple/tuple.hpp>
#include <boost/format.hpp>
#include <vector>
#include "Config.h"

namespace TestTask
{
	#define foreach BOOST_FOREACH
	#define sptr boost::shared_ptr
	#define scomptr CComPtr

	#define declaresmart(T)		 typedef sptr<T> P##T;
	#define declareCOMsmart(T,S) typedef scomptr<T> S;
		
	typedef HRESULT						hres;
	typedef IDirect3DDevice9*			PDevice;
	typedef std::vector<std::wstring>   Text;
	
	declareCOMsmart( IDirect3DVertexBuffer9, PVertexBuffer )
	declareCOMsmart( IDirect3DSurface9,		 PSurface )
	declareCOMsmart( IDirect3DTexture9,      PTexture )
	declareCOMsmart( ID3DXMesh,		         PMesh )
	declareCOMsmart( ID3DXEffect,			 PEffect );
	declareCOMsmart( IDirect3DQuery9,		 PQuery );

	typedef D3DXMATRIX					Matrix;
	typedef D3DXVECTOR3					Vector3;
	typedef D3DXCOLOR					Color;
	typedef D3DXHANDLE					XHandle;
	
	const float   pi = atan(1.0f)*4;
	const float   hpi = atan(1.0f)*2;
	const Vector3 zero  = Vector3(0,0,0);
	const Color   white = Color(1,1,1,1);


	hres   createSphere( float radius, PDevice, PMesh& );
	hres   createArrow( PDevice, PMesh& );
	hres   createScreenQuad( PDevice, int width, int height, PVertexBuffer& );
	hres   renderScreenQuad( PDevice, PVertexBuffer& );
		
	bool   intersectsRay( Vector3 orig, Vector3 dir, Vector3 boxmin, Vector3 boxmax, Vector3& point);
	void   getRayFromCamera( Vector3* origin, Vector3* direction, Matrix& world, Matrix& view, Matrix& proj );
	void   computeAABB( const PMesh& mesh, Vector3& box_min, Vector3& box_max );
	
	Vector3 perpen( const Vector3& a );
	void    forward_axis( const Vector3& base, Vector3& up, Vector3& right );
	Vector3 sphere_point( const Vector3& center, float radius, const Vector3& ax, const Vector3& ay,
						  const Vector3& az,  float an1, float an2 );

	#define checked(x) { hres hr = (x); if( FAILED(hr) ) { return hr; } }
	#define release SAFE_RELEASE

	// simple scene graph support ------------------------

	struct RenderPackage{

		PDevice device;
		PEffect effect;
		Matrix  view;
		Matrix  proj;
		Matrix  viewProj;
		bool	isDrugged;
		float   time;
		float	elapsed;

		RenderPackage( PDevice device, PEffect effect, const Matrix& view, const Matrix& proj, bool isDrugged, float time, float elapsed ):
			device(device),effect(effect),view(view),proj(proj),isDrugged(isDrugged),time(time),elapsed(elapsed){}
	};

	class RenderObject;
	declaresmart(RenderObject)
	typedef std::vector< PRenderObject > Renders;

	class Border{
		public:
			virtual void GetMoveHere( const Vector3& point, Vector3& suggest ) = 0;
	};

	declaresmart( Border )

	class SetEffectParameter{
		public:
			SetEffectParameter( const char* name ):name(name){}
			template<class T> hres operator()( PEffect effect, const T& value );

		private:
			typedef std::pair<PEffect,XHandle> Desc;
			typedef std::vector<Desc>		   Descs;
		
			XHandle init(PEffect effect);

			const char* name;
			Descs		descs;
	};

	class RenderObject{
		public:
						   RenderObject( Vector3 position = zero, 
										 Color diffuse = white, 
										 XHandle renderTech = "RenderScene" );
			
			RenderObject*  Append(PRenderObject child);
			
			virtual void   Render( RenderPackage& rp );

			hres		   RenderMain( RenderPackage& rp );
			hres		   RenderWithEffect( RenderPackage& rp );
			
			const Vector3& GetPosition();
			void		   SetPosition( const Vector3& pos );
			void		   SetScale( float v );

			virtual void   Move( Vector3& offset );
			void		   ApplyTransform();
	
		protected:

			PMesh			mesh;
			UINT			subsetCount;

			XHandle			renderTech;
			Matrix			worldMatrix, rotation, scale;
			Vector3			position;
			Color			diffuse;

			RenderObject*	master;
			Renders			childs;

			SetEffectParameter setWorld, setWorldViewProj, setDiffuseColor;
	};

	// ----------------------------

	template<class Vertex>
	hres createVertexBuffer( PDevice device, UINT size, PVertexBuffer& output){
		return device->CreateVertexBuffer( size * sizeof( Vertex ), 0, Vertex::fvf, D3DPOOL_DEFAULT, &output, NULL );				
	}

	template<class Vertex>
	hres writeVertexBuffer( Vertex* vertices, UINT size, PVertexBuffer vb){
		void* buffer;
		checked( vb->Lock( 0, sizeof( Vertex )*size, ( void** )&buffer, 0 ) )
		memcpy( buffer, vertices, sizeof( Vertex )*size );
		return vb->Unlock();
	}

	template<class Vertex>
	hres renderVertexBuffer( PDevice device, PVertexBuffer vp, D3DPRIMITIVETYPE pt, UINT StartVertex,UINT PrimitiveCount ){
		device->SetStreamSource( 0, vp, 0, sizeof( Vertex ) );
		device->SetFVF(Vertex::fvf);
		return device->DrawPrimitive( pt, StartVertex, PrimitiveCount );
	}

	hres createTexture( PDevice device, UINT width, UINT height, DWORD Usage, D3DFORMAT Format, PTexture& texture );
	hres writeTexture( PTexture texture, void* data, UINT size );

	template<class T>
	T fit(T vl, T mn, T mx){
		return ( vl > mx ) ? mx : ((vl < mn) ? mn : vl);
	}
	
	void   float_to_float2( float vl, float& x1, float& x2 );
	float  float2_to_float( float& x1, float& x2 );
	Color  float2_to_color( float vl1, float vl2 );
	void   color_to_2float( Color cl, float& vl1, float& vl2 );
	Color  float_to_color( float vl );
	float  color_to_float(Color color);

	//

	template<> inline hres SetEffectParameter::operator()<Vector3>( PEffect effect, const Vector3& v){ 
		return effect->SetFloatArray( init( effect ), (const float*)(&v), 3);
	}

	template<> inline hres SetEffectParameter::operator()<float>( PEffect effect, const float& v){ 
		return effect->SetFloat( init( effect ), v);
	}

	template<> inline hres SetEffectParameter::operator()<Matrix>( PEffect effect, const Matrix& v){ 
		return effect->SetMatrix( init( effect ), &v);
	}

	template<> inline hres SetEffectParameter::operator()<Color>( PEffect effect, const Color& v){ 
		return effect->SetFloatArray( init( effect ), (const float*)&v, 4);
	}

	template<> inline hres SetEffectParameter::operator()<PTexture>( PEffect effect, const PTexture& v){
		return effect->SetTexture( init( effect ), v);
	}
}

